﻿using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.Speech.Recognition;
using System.Speech.Recognition.SrgsGrammar;

using Haro.Speech;

namespace Haro.Core
{
    public static class Prime
    {
        private static ModuleHost m_ModuleHost;

        #region InitializeHaro
        /// <summary>
        /// Initializes all of the system processes like speech and the module host.
        /// </summary>
        public static void InitializeHaro()
        {
            // Creates the Resources and Modules directory if they do not exist.
            if (!Directory.Exists(Globals.ResoucesDirectory))
                Directory.CreateDirectory(Globals.ResoucesDirectory);
            if (!Directory.Exists(Globals.ModulesDirectory))
                Directory.CreateDirectory(Globals.ModulesDirectory);

            // Initializes the Speech Output system.
            SpeechOutput.InitializeSpeechOutput();

            // Creates the ModuleHost, initialize settings and creates initial grammar if not present.
            m_ModuleHost = new ModuleHost();
            if (!File.Exists(Globals.GrammarLocation))
                CreateGrammar();

            // Now that the initial grammar has been created, the Speech Input system is initialized.
            SpeechInput.InitializeSpeechInput(new EventHandler<SpeechRecognizedEventArgs>(m_Recognizer_SpeechRecognized));
            SpeechInput.LoadGrammar(Globals.HostAppName, Globals.GrammarLocation);

            // Loads the settings for Haro.
            LoadHaroSettings();

            // At the end, the grammar is composed ready to be used, the old grammar is unloaded and the
            // new one is loaded.
            CreateGrammar();
            SpeechInput.ReloadGrammar(Globals.HostAppName);

            // Assign the proper event handlers for the ModuleHost
            m_ModuleHost.ModuleGrammarLoaded += new ModuleEventHandler(m_ModuleHost_GrammarLoaded);
            m_ModuleHost.ModuleGrammarUnloaded += new ModuleEventHandler(m_ModuleHost_GrammarUnloaded);
            // The following event has no real importance as of now, but in the future this can be used as felt necessary.
            // m_ModuleHost.GrammarReloaded += new ModuleEventHandler(m_ModuleHost_GrammarReloaded);
        }
        #endregion

        #region CreateGrammar
        private static void CreateGrammar()
        {
            #region SrgsDocument & RootRule Init
            // Initializes the grammar document and root rule.
            SrgsDocument doc = new SrgsDocument();
            SrgsRule rootRule = new SrgsRule("Output");
            rootRule.Scope = SrgsRuleScope.Public;
            doc.Root = rootRule;
            doc.Mode = SrgsGrammarMode.Voice;
            #endregion

            #region HaroRule
            SrgsRule haroRule = new SrgsRule("HaroRule");
            haroRule.Elements.Add(new SrgsItem(0, 1, "Haro!"));

            // Adds the Haro rule to the grammar.
            rootRule.Elements.Add(new SrgsRuleRef(haroRule));
            doc.Rules.Add(haroRule);
            #endregion

            #region Loaded Module Names
            // Adds all the modules in the loaded module list to a SrgsOneOf.
            SrgsRule loadedModuleNames = new SrgsRule("LoadedModuleNames");
            SrgsOneOf _modulesNamesOneOf = new SrgsOneOf();

            foreach (string s in m_ModuleHost.GetLoadedModuleNames())
                _modulesNamesOneOf.Add(new SrgsItem(s));

            if (_modulesNamesOneOf.Items.Count > 0)
            {
                loadedModuleNames.Add(_modulesNamesOneOf);
                doc.Rules.Add(loadedModuleNames);
            }

            _modulesNamesOneOf = null;
            #endregion

            #region Nonloaded Module Names
            // Adds all the modules in the non-loaded module list to a SrgsOneOf.
            SrgsRule nonloadedModuleNames = new SrgsRule("NonloadedModuleNames");
            _modulesNamesOneOf = new SrgsOneOf();

            foreach (string s in m_ModuleHost.GetNonloadedModuleNames())
                _modulesNamesOneOf.Add(new SrgsItem(s));

            if (_modulesNamesOneOf.Items.Count > 0)
            {
                nonloadedModuleNames.Add(_modulesNamesOneOf);
                doc.Rules.Add(nonloadedModuleNames);
            }

            _modulesNamesOneOf = null;
            #endregion

            #region Various Commands for Haro
            // Adds all the commands defined to the root rule's SrgsOneOf section.
            SrgsOneOf rootOneOf = new SrgsOneOf();
            foreach (KeyValuePair<string, Globals.CommandType> kvp in Globals.Commands)
            {
                if (kvp.Value != Globals.CommandType.LoadModule && kvp.Value != Globals.CommandType.UnloadModule)
                    rootOneOf.Add(new SrgsItem(kvp.Key));
                else if (kvp.Value == Globals.CommandType.LoadModule && nonloadedModuleNames.Elements.Count > 0)
                    rootOneOf.Add(new SrgsItem(new SrgsItem(kvp.Key), new SrgsRuleRef(nonloadedModuleNames)));
                else if (kvp.Value == Globals.CommandType.UnloadModule && loadedModuleNames.Elements.Count > 0)
                    rootOneOf.Add(new SrgsItem(new SrgsItem(kvp.Key), new SrgsRuleRef(loadedModuleNames)));
            }

            if (rootOneOf.Items.Count > 0)
                rootRule.Elements.Add(rootOneOf);
            #endregion

            #region SrgsDocument Finalization
            // Adds the rootRule to the document finally.
            doc.Rules.Add(rootRule);

            // Creates the grxml file and writes all the grammar to it.
            XmlWriter writer = XmlWriter.Create(Globals.GrammarLocation);
            doc.WriteSrgs(writer);
            writer.Close();

            // Cleanup of resources used during this procedure.
            haroRule = null;
            loadedModuleNames = null;
            nonloadedModuleNames = null;
            rootRule = null;
            doc = null;
            writer = null;
            #endregion
        }
        #endregion

        #region Saving and Loading of Haro's settings
        /// <summary>
        /// Loads Haro's settings from the settings path defined.  If that file does not exist, a new one is created.
        /// </summary>
        private static void LoadHaroSettings()
        {
            Globals.HaroSettings = (Settings)m_ModuleHost.LoadSettings(Globals.SettingsLocation, typeof(Settings));
            if (Globals.HaroSettings != null)
            {
                Globals.HaroSettings.ApplySettings();

                if (Globals.HaroSettings.ModulesToLoadAtStartup != null)
                {
                    foreach (string name in Globals.HaroSettings.ModulesToLoadAtStartup)
                        m_ModuleHost.LoadModule(name);
                }
            }
            else SaveHaroSettings();
        }

        /// <summary>
        /// Gathers the current settings for Haro and saves them to the settings path as defined.
        /// </summary>
        private static void SaveHaroSettings()
        {
            if (Globals.HaroSettings == null)
            {
                Globals.HaroSettings = new Settings();
                Globals.HaroSettings.ApplySettings();
            }
            Globals.HaroSettings.GatherSettings();
            m_ModuleHost.SaveSettings(Globals.SettingsLocation, typeof(Settings), Globals.HaroSettings);
        }
        #endregion

        #region SpeechRecognized Event Handler
        private static void m_Recognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            if (e.Result.Grammar.Name != Globals.HostAppName)
                m_ModuleHost.SendMessage(e.Result.Grammar.Name, e.Result.Text);
            else OnVerbalCommand(e.Result.Text);
        }
        #endregion

        #region ModuleHost Event Handlers
        private static void m_ModuleHost_GrammarUnloaded(string ModuleName)
        {
            CreateGrammar();
            SpeechInput.ReloadGrammar(Globals.HostAppName);
        }

        private static void m_ModuleHost_GrammarLoaded(string ModuleName)
        {
            CreateGrammar();
            SpeechInput.ReloadGrammar(Globals.HostAppName);
        }
        #endregion

        #region OnVerbalCommand
        /// <summary>
        /// Performs a particular task pertaining to command passed to it.
        /// </summary>
        /// <param name="Command">String containing the command.</param>
        private static void OnVerbalCommand(string Command)
        {
            Globals.CommandType ct = Globals.CommandType.None;

            if (Globals.Commands.ContainsKey(Command))
                ct = Globals.Commands[Command];

            switch (ct)
            {
                case Globals.CommandType.DebugOff:
                    if (!Debug.Enabled)
                        SpeechOutput.Speak("Debug mode is already off.");
                    else
                    {
                        Debug.Enabled = false;
                        Globals.HaroSettings.GatherSettings();
                        SaveHaroSettings();
                        SpeechOutput.Speak("Debug mode off.");
                    }
                    break;

                case Globals.CommandType.DebugOn:
                    if (Debug.Enabled)
                        SpeechOutput.Speak("Debug mode is already on.");
                    else
                    {
                        Debug.Enabled = true;
                        Globals.HaroSettings.GatherSettings();
                        SaveHaroSettings();
                        SpeechOutput.Speak("Debug mode on.");
                    }
                    break;

                case Globals.CommandType.Goodbye:
                    System.Windows.Forms.Application.Exit();
                    break;

                case Globals.CommandType.ListModules:
                    string[] names = m_ModuleHost.GetLoadedModuleNames();
                    if (names == null || names.Length == 0)
                        SpeechOutput.Speak("There are no modules currently loaded.");
                    else
                    {
                        SpeechOutput.Speak("The following are the loaded modules.");
                        foreach (string s in names)
                            SpeechOutput.Speak(s);
                    }
                    names = m_ModuleHost.GetNonloadedModuleNames();
                    if (names == null || names.Length == 0)
                        SpeechOutput.Speak("There are no non-loaded modules.");
                    else
                    {
                        SpeechOutput.Speak("The following are the non-loaded modules.");
                        foreach (string s in names)
                            SpeechOutput.Speak(s);
                    }
                    break;

                case Globals.CommandType.Mute:
                    SpeechOutput.Speak("Keeping quiet.");
                    SpeechOutput.Volume = 0;
                    Globals.HaroSettings.GatherSettings();
                    SaveHaroSettings();
                    break;

                case Globals.CommandType.None:
                    bool matchFound = false;
                    foreach (KeyValuePair<string, Globals.CommandType> kvp in Globals.Commands)
                    {
                        if (kvp.Value == Globals.CommandType.LoadModule && Command.Contains(kvp.Key))
                        {
                            foreach (string name in m_ModuleHost.GetNonloadedModuleNames())
                            {
                                if (Command.Equals(kvp.Key.Trim() + " " + name.Trim()))
                                {
                                    m_ModuleHost.LoadModule(name);
                                    matchFound = true;
                                    break;
                                }
                            }
                        }
                        else if (kvp.Value == Globals.CommandType.UnloadModule && Command.Contains(kvp.Key))
                        {
                            foreach (string name in m_ModuleHost.GetLoadedModuleNames())
                            {
                                Debug.Output(kvp.Key + name);
                                if (Command.Equals(kvp.Key.Trim() + " " + name.Trim()))
                                {
                                    m_ModuleHost.UnloadModule(name);
                                    matchFound = true;
                                    break;
                                }
                            }
                        }

                        if (matchFound)
                            break;
                    }
                    break;

                case Globals.CommandType.ScanAndLoadAllModules:
                    m_ModuleHost.LoadAllModules();
                    SpeechOutput.Speak("Finished loading all modules.");
                    break;

                case Globals.CommandType.ScanForModules:
                    m_ModuleHost.ScanForNewModules();
                    break;

                case Globals.CommandType.Unmute:
                    SpeechOutput.Speak("Yay!  I can speak again.");
                    SpeechOutput.Volume = 100;
                    Globals.HaroSettings.GatherSettings();
                    SaveHaroSettings();
                    break;

                default: break;
            }
        }
        #endregion

        #region Cleanup
        /// <summary>
        /// Performs a cleanup operation on all of Haro's system processes.
        /// </summary>
        public static void Cleanup()
        {
            m_ModuleHost.Cleanup();
            m_ModuleHost = null;
            SpeechInput.Cleanup();
            SpeechOutput.Cleanup();
        }
        #endregion
    }
}
